﻿using System;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;

namespace CarBloke
{
    public class VehicleMath
    {
        public static VehicleStats CalculateVehicleStats(Guid vehicleID)
        {
            var vehicleLogs = App.LoadLogEntries(vehicleID).OrderBy((log) => log.LogTime);

            VehicleStats stats = new VehicleStats(vehicleID);
            calculateFuelEfficiency(ref stats, vehicleLogs);
            calclateCost(ref stats, vehicleLogs);
            calculateOdometer(ref stats, vehicleLogs);
            calculateDistanceSinceOiLChange(ref stats, vehicleLogs);

            return stats;
        }

        private static void calculateFuelEfficiency(ref VehicleStats stats, IEnumerable<LogEntry> logs)
        {
            List<Triple<Double, Double, DateTime>> weightsEfficienciesDates = new List<Triple<Double, Double, DateTime>>();

            //FuelEfficiency
            LogEntry previousEntry = null;
            Double fuelAccumulator = 0;
            Double distanceAccumulator = 0;
            var fuelLogs = logs.Where(log => log.LogType == LogEntry.LogTypes.Fuel).OrderBy(log => log.LogTime);
            foreach (LogEntry logEntry in fuelLogs)
            {
                if (logEntry.MissedLogs)
                {
                    distanceAccumulator = 0;
                    fuelAccumulator = 0;
                    previousEntry = null;
                    continue;
                }

                if (previousEntry == null)
                {
                    previousEntry = logEntry;
                    continue;
                }

                distanceAccumulator += (logEntry.OdometerReading - previousEntry.OdometerReading);
                fuelAccumulator += logEntry.FuelAdded;
                if (logEntry.FullTank)
                {
                    if (fuelAccumulator == 0)
                    {
                        distanceAccumulator = 0;
                        fuelAccumulator = 0;
                        continue;
                    }

                    double efficiency = distanceAccumulator / fuelAccumulator;
                    stats.FuelEfficiency.Add(new DateValue<Double>(logEntry.LogTime, efficiency));
                    weightsEfficienciesDates.Add(new Triple<Double, Double, DateTime>(distanceAccumulator, efficiency, logEntry.LogTime));
                    distanceAccumulator = 0;
                    fuelAccumulator = 0;
                }

                previousEntry = logEntry;
            }


            //MostRecentFuelEfficiency
            if (stats.FuelEfficiency.Count > 0)
            {
                var mostRecent = stats.FuelEfficiency.Last();
                stats.MostRecentFuelEfficiency = mostRecent.Value;
                stats.MostRecentFuelEfficiencyDate = mostRecent.Date;
            }


            //AverageFuelEfficiency            
            double averageEfficiency = double.NaN;
            if (weightsEfficienciesDates.Count > 0)
            {
                Double weightTotal = 0;
                foreach (var weightEfficiency in weightsEfficienciesDates)
                {
                    weightTotal += weightEfficiency.A;
                }

                if (weightTotal != 0)
                {
                    averageEfficiency = 0;
                    foreach (var weightEfficiency in weightsEfficienciesDates)
                    {
                        averageEfficiency += (weightEfficiency.A / weightTotal) * weightEfficiency.B;
                    }
                }
            }
            stats.AverageFuelEfficiency = averageEfficiency;


            //FuelEfficiencyMonthlyAverage
            DateTime runningDate = DateTime.MinValue;
            double monthlyWeight = 0;
            List<Triple<Double, Double, DateTime>> monthlyWED = null;
            foreach (var wed in  weightsEfficienciesDates.OrderBy(x => x.C))
            {
                if (wed.C.Year != runningDate.Year || wed.C.Month != runningDate.Month)
                {
                    //Before we move onto the next month we need to pass through the month again and calculate the average
                    if (monthlyWED != null)
                    {
                        double monthEfficiencyAverage = 0;
                        foreach (var mWed in monthlyWED)
                        {
                            monthEfficiencyAverage += mWed.A / monthlyWeight * mWed.B;
                        }
                        stats.FuelEfficiencyMonthlyAverage.Add(new DateValue<double>(new DateTime(runningDate.Year, runningDate.Month, 1), monthEfficiencyAverage));
                    }

                    //OK, move on the the next month
                    monthlyWED = new List<Triple<Double, Double, DateTime>>();
                    monthlyWeight = 0;
                    runningDate = wed.C;
                }

                monthlyWeight += wed.A;
                monthlyWED.Add(wed);
            }

            if (monthlyWED != null)
            {
                double monthEfficiencyAverage = 0;
                foreach (var mWed in monthlyWED)
                {
                    monthEfficiencyAverage += mWed.A / monthlyWeight * mWed.B;
                }
                stats.FuelEfficiencyMonthlyAverage.Add(new DateValue<double>(new DateTime(runningDate.Year, runningDate.Month, 1), monthEfficiencyAverage));
            }
        }

        private static void calclateCost(ref VehicleStats stats, IEnumerable<LogEntry> logs)
        {
            //Cost
            foreach (LogEntry logEntry in logs)
            {
                if (!Double.IsNaN(logEntry.Cost) && !Double.IsInfinity(logEntry.Cost))
                {
                    stats.Cost.Add(new DateValue<double>(logEntry.LogTime, logEntry.Cost));
                }
            }

            //CostMonthlyTotal
            double runningMonthTotal = double.NaN;
            DateTime runningMonth = DateTime.MinValue;
            foreach (LogEntry logEntry in logs.OrderBy(log => log.LogTime))
            {
                if (!Double.IsNaN(logEntry.Cost) && !Double.IsInfinity(logEntry.Cost))
                {
                    if (logEntry.LogTime.Year == runningMonth.Year && logEntry.LogTime.Month == runningMonth.Month)
                    {
                        runningMonthTotal += logEntry.Cost;
                    }
                    else if (!Double.IsNaN(runningMonthTotal))
                    {
                        stats.CostMonthlyTotal.Add(new DateValue<Double>(new DateTime(runningMonth.Year, runningMonth.Month, 1), runningMonthTotal));
                        runningMonthTotal = logEntry.Cost;
                    }
                    else
                    {
                        runningMonthTotal = logEntry.Cost;
                    }

                    runningMonth = logEntry.LogTime;
                }
            }

            if (!Double.IsNaN(runningMonthTotal))
            {
                stats.CostMonthlyTotal.Add(new DateValue<Double>(new DateTime(runningMonth.Year, runningMonth.Month, 1), runningMonthTotal));
            }
        }

        private static void calculateOdometer(ref VehicleStats stats, IEnumerable<LogEntry> logs)
        {
            //Odometer
            foreach (LogEntry logEntry in logs)
            {
                if (!Double.IsNaN(logEntry.OdometerReading) && !Double.IsInfinity(logEntry.OdometerReading))
                {
                    stats.Odometer.Add(new DateValue<double>(logEntry.LogTime, logEntry.OdometerReading));
                }
            }

            //OdometerMonthlyTotal
            double runningMonthTotal = double.NaN;
            DateTime runningMonth = DateTime.MinValue;
            foreach (LogEntry logEntry in logs.OrderBy(log => log.LogTime))
            {
                if (!Double.IsNaN(logEntry.OdometerReading) && !Double.IsInfinity(logEntry.OdometerReading))
                {
                    if (logEntry.LogTime.Year == runningMonth.Year && logEntry.LogTime.Month == runningMonth.Month)
                    {
                        runningMonthTotal += logEntry.OdometerReading;
                    }
                    else if (!Double.IsNaN(runningMonthTotal))
                    {
                        stats.OdometerMonthlyTotal.Add(new DateValue<Double>(new DateTime(runningMonth.Year, runningMonth.Month, 1), runningMonthTotal));
                        runningMonthTotal = logEntry.OdometerReading;
                    }
                    else
                    {
                        runningMonthTotal = logEntry.OdometerReading;
                    }

                    runningMonth = logEntry.LogTime;
                }
            }

            if (!Double.IsNaN(runningMonthTotal))
            {
                stats.OdometerMonthlyTotal.Add(new DateValue<Double>(new DateTime(runningMonth.Year, runningMonth.Month, 1), runningMonthTotal));
            }
        }

        private static void calculateDistanceSinceOiLChange(ref VehicleStats stats, IOrderedEnumerable<LogEntry> vehicleLogs)
        {
            var oilLog = vehicleLogs.OrderByDescending(log => log.LogTime).FirstOrDefault(log => log.LogType == LogEntry.LogTypes.Oil);
            if (oilLog != null)
            {
                double oilOdomter = oilLog.OdometerReading;
                double mostRecentOdomoter = stats.Odometer.Max(odo => odo.Value);

                stats.DistanceSinceOilChange = mostRecentOdomoter - oilOdomter;
            }
        }

        internal static double CalculateFuelEfficiency(LogEntry olderLog, LogEntry newerLog)
        {
            double result = double.NaN;
            if (olderLog != null && newerLog != null)
            {
                if (!newerLog.MissedLogs && newerLog.FullTank && newerLog.FuelAdded != 0)
                {
                    double distanceDifferenceKm = newerLog.OdometerReading - olderLog.OdometerReading;
                    double efficiencyKmL = distanceDifferenceKm / newerLog.FuelAdded;

                    result = efficiencyKmL;
                }
            }

            return result;
        }
    }
}
